/**
 * @(#)RefleccionMetodos.java Nov 5, 2006
 *
 * Copyright (c) 2005-2010 Psiqueware S.A. de C.V. All Rights Reserved.
 * This software is the proprietary information Psiqueware S.A. de C.V.
 * Use is subject to license terms.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *
 * <br/>Usuario : AngelElectrico
 * <br/>Creaci&oacute;n: Nov 5, 2006 - 9:36:15 PM
 * <br/>
 * <br/>
 * <br/><t5>Cambios :</t5>
 * <br/><b>Fecha | Autor  |  Cambios</b>
 * <ul>
 * <li>Nov 5, 2006 | <a href = "mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a> | Creaci&oacute;n del Objeto.</li>
 * <li>Nov 06, 2006 | <a href = "mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a> | Agregado m&eacute;todo para clonar.</li>
 * <li>Nov 06, 2006 | <a href = "mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a> | Se refactora metodo para hacer may&uacute;scula la primera letra de un atributo.</li>
 * <li>Sep 25, 2008 | <a href = "mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a> | Se agregan nuevos m&eacute;todos.</li>
 * </ul>
 */

package com.psiqueware.alheli.reflection;

import com.psiqueware.alheli.exception.NullArgumentException;
import com.psiqueware.alheli.exception.NullPostConditionException;
import com.psiqueware.alheli.logging.Logger;
import com.psiqueware.alheli.logging.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.StringTokenizer;

/**
 * Metodos utilitarios para obtener informacion de un bean por reflexion.
 * <br/>Paquete Original: com.psiqueware.basico.reflection
 * <br/>Nombre  Original: RefleccionMetodos
 * <br/>Oprima para ver : <a href="RefleccionMetodos.java.html"><i>C&oacute;digo Fuente</i> </a>
 *
 * @author <a href="mailto:AngelElectrico@PsiqueWare.com">Angel El&eacute;ctrico</a>
 * @version Revision: 1.1 Nov 5, 2006 9:36:15 PM
 * @noinspection ClassWithoutLogger, DuplicateStringLiteralInspection
 */
public final class RefleccionMetodos
{

    /**
     * Logger de clase para RefleccionMetodos
     */
    private static final Logger s_log = LoggerFactory.getLogger(RefleccionMetodos.class);

    /**
     * Constructor de RefleccionMetodos.
     */
    public RefleccionMetodos()
    {

    }

    /**
     * ThreadSafeSimpleDateFormat para una fecha sencilla.
     */
    public static final ThreadSafeSimpleDateFormat S_SDF_FECHA = new ThreadSafeSimpleDateFormat("yyyy/MM/dd");

    /**
     * ThreadSafeSimpleDateFormat para una hora sin segundos.
     */
    public static final ThreadSafeSimpleDateFormat S_SDF_HORA = new ThreadSafeSimpleDateFormat("HH:mm");

    /**
     * ThreadSafeSimpleDateFormat para una hora con segundos.
     */
    public static final ThreadSafeSimpleDateFormat S_SDF_HORA_EXT = new ThreadSafeSimpleDateFormat("HH:mm:ss");

    /**
     * ThreadSafeSimpleDateFormat para una fecha y hora.
     */
    public static final ThreadSafeSimpleDateFormat S_SDF_FECHA_HORA = new ThreadSafeSimpleDateFormat("yyyy/MM/dd HH:mm");

    /**
     * ThreadSafeSimpleDateFormat para una fecha y hora con segundos.
     */
    public static final ThreadSafeSimpleDateFormat S_SDF_FECHA_HORA_EXT = new ThreadSafeSimpleDateFormat("yyyy/MM/dd HH:mm:ss");

    /**
     * Ejecuta una operaci&oacute;n en la instancia dada pasando como par&aacute;metro un casting/conversion al objeto correcto de la cadena par&aacute;metro.
     *
     * @param instancia    donde se va a ejecutar el setter.
     * @param parametro    cadena con el par&aacute;metro buscado.
     * @param nombreMetodo nombre dla operaci&oacute;n setter a invocar.
     *
     * @throws NoSuchMethodException                       en caso de no encontrarse el metodo.
     * @throws IllegalAccessException                      en caso de no ser un metodo publico.
     * @throws java.lang.reflect.InvocationTargetException en caso de que el meotod ejecutado suelte una excepcion.
     * @throws java.text.ParseException                    en caso de no poder procesar una fecha
     * @noinspection OverlyComplexMethod, OverlyLongMethod, MethodWithTooExceptionsDeclared, MethodWithMoreThanThreeNegations, AssignmentToMethodParameter, CallToStringEqualsIgnoreCase, CallToStringEquals, MethodWithMultipleLoops
     */
    public static void ejecutaMetodoSetterBasicoCasting(Object instancia, String parametro, String nombreMetodo) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, ParseException
    {
        //Validamos
        if (null == instancia)
            throw new NullArgumentException("<error>El argumento 'instancia' no puede ser NULL.</error>");
        if (null == parametro)
            throw new NullArgumentException("<error>El argumento 'parametro' no puede ser NULL.</error>");
        if (null == nombreMetodo)
            throw new NullArgumentException("<error>El argumento 'nombreMetodo' no puede ser NULL.</error>");
        if (!isMetodoExistente(instancia.getClass(), nombreMetodo))
            throw new IllegalArgumentException("<error>la operaci&oacute;n '" + nombreMetodo + "' no existe en la clase: " + instancia.getClass() + ".</error>");

        //Buscamos la operaci&oacute;n.
        final Class instanciaClass = instancia.getClass();
        final Method[] methods = instanciaClass.getMethods();
        Method method = null;
        for (final Method methodIter : methods)
        {
            if (methodIter.getName().equals(nombreMetodo))
            {
                method = methodIter;
                break;
            }
        }
        if (!isSetterBasico(method))
            throw new IllegalArgumentException("<error>la operaci&oacute;n " + nombreMetodo + " no es un setter b&aacute;sico.</error>");

        assert null != method;
        final Class clazz = method.getParameterTypes()[0];

        //Hacemos el casting y realizamos las llamadas.
        if (clazz.isAssignableFrom(Character.class) || clazz.isAssignableFrom(char.class))
        {
            final char[] chars = parametro.toCharArray();
            if (0 != chars.length)
            {
                ejecutaMetodoSetter(instancia, chars[0], nombreMetodo);
            }
        }
        else if (clazz.isAssignableFrom(String.class))
        {
            ejecutaMetodoSetter(instancia, parametro, nombreMetodo);
        }
        else if (clazz.isAssignableFrom(Boolean.class) || clazz.isAssignableFrom(boolean.class))
        {
            parametro = parametro.trim();
            if ("1".equals(parametro)) parametro = "true";
            else if ("verdadero".equalsIgnoreCase(parametro)) parametro = "true";
            else if ("t".equalsIgnoreCase(parametro)) parametro = "true";
            else if ("v".equalsIgnoreCase(parametro)) parametro = "true";
            else if ("0".equals(parametro)) parametro = "false";
            else if ("falso".equalsIgnoreCase(parametro)) parametro = "false";
            else if ("f".equalsIgnoreCase(parametro)) parametro = "false";
            final Boolean aBoolean = Boolean.valueOf(parametro);
            ejecutaMetodoSetter(instancia, aBoolean, nombreMetodo);
        }
        else if (clazz.isAssignableFrom(java.util.Date.class))
        {
            parametro = parametro.trim();
            java.util.Date aDate = parseaFecha(parametro);
            ejecutaMetodoSetter(instancia, aDate, nombreMetodo);
        }
        else if (clazz.isAssignableFrom(java.sql.Date.class))
        {
            parametro = parametro.trim();
            java.util.Date aDate = parseaFecha(parametro);
            java.sql.Date aSqlDate = new java.sql.Date(aDate.getTime());
            ejecutaMetodoSetter(instancia, aSqlDate, nombreMetodo);
        }
        else if (clazz.isAssignableFrom(Timestamp.class))
        {
            parametro = parametro.trim();
            java.util.Date aDate = parseaFecha(parametro);
            java.sql.Timestamp aSqlDate = new java.sql.Timestamp(aDate.getTime());
            ejecutaMetodoSetter(instancia, aSqlDate, nombreMetodo);
        }
        else if (clazz.isEnum())
        {
            Object aEnumConstant = null;
            parametro = parametro.trim();
            for (Object o : clazz.getEnumConstants())
            {
                final String strEnumConstant = String.valueOf(o);
                if (strEnumConstant.equals(parametro))
                {
                    aEnumConstant = o;
                }
            }
            ejecutaMetodoSetter(instancia, aEnumConstant, nombreMetodo);
        }
        else
        {
            parametro = parametro.trim();
            if (0 == parametro.length()) parametro = "0";

            if (clazz.isAssignableFrom(Short.class) || clazz.isAssignableFrom(short.class))
            {
                final Short aShort = Short.valueOf(parametro);
                ejecutaMetodoSetter(instancia, aShort, nombreMetodo);
            }
            else if (clazz.isAssignableFrom(Byte.class) || clazz.isAssignableFrom(byte.class))
            {
                final Byte aByte = Byte.valueOf(parametro);
                ejecutaMetodoSetter(instancia, aByte, nombreMetodo);
            }
            else if (clazz.isAssignableFrom(Integer.class) || clazz.isAssignableFrom(int.class))
            {
                final Integer integer = Integer.valueOf(parametro);
                ejecutaMetodoSetter(instancia, integer, nombreMetodo);
            }
            else if (clazz.isAssignableFrom(Long.class) || clazz.isAssignableFrom(long.class))
            {
                final Long aLong = Long.valueOf(parametro);
                ejecutaMetodoSetter(instancia, aLong, nombreMetodo);
            }
            else if (clazz.isAssignableFrom(Float.class) || clazz.isAssignableFrom(float.class))
            {
                final Float aFloat = Float.valueOf(parametro);
                ejecutaMetodoSetter(instancia, aFloat, nombreMetodo);
            }
            else if (clazz.isAssignableFrom(Double.class) || clazz.isAssignableFrom(double.class))
            {
                final Double aDouble = Double.valueOf(parametro);
                ejecutaMetodoSetter(instancia, aDouble, nombreMetodo);
            }
            else if (clazz.isAssignableFrom(BigDecimal.class))
            {
                final BigDecimal aBigDecimal = new BigDecimal(parametro);
                ejecutaMetodoSetter(instancia, aBigDecimal, nombreMetodo);
            }
            else if (clazz.isAssignableFrom(BigInteger.class))
            {
                final BigInteger aBigInteger = new BigInteger(parametro);
                ejecutaMetodoSetter(instancia, aBigInteger, nombreMetodo);
            }

        }

    }

    /**
     * Obtiene una fecha a partir de una cadena.
     *
     * @param parametro de la fecha.
     *
     * @return Date.
     *
     * @throws ParseException en caso de no poder ob
     */
    private static java.util.Date parseaFecha(final String parametro) throws ParseException
    {
        final java.util.Date aDate;

        if (0 == parametro.length())
        {
            aDate = new Date();
        }
        else
        {
            final String parametroConSustitucion = parametro.trim().replace("\\", "/");
            if (19 == parametroConSustitucion.length() && parametroConSustitucion.contains("/") && parametroConSustitucion.contains(":"))
            {
                aDate = S_SDF_FECHA_HORA_EXT.parse(parametroConSustitucion);
            }
            else if (8 < parametroConSustitucion.length() && parametroConSustitucion.contains("/") && !parametroConSustitucion.contains(":"))
            {
                aDate = S_SDF_FECHA.parse(parametroConSustitucion);
            }
            else if (8 == parametroConSustitucion.length() && parametroConSustitucion.contains(":"))
            {
                aDate = S_SDF_HORA_EXT.parse(parametroConSustitucion);
            }
            else if (5 == parametroConSustitucion.length() && parametroConSustitucion.contains(":"))
            {
                aDate = S_SDF_HORA.parse(parametroConSustitucion);
            }
            else
            {
                aDate = S_SDF_FECHA_HORA.parse(parametroConSustitucion);
            }
        }
        return aDate;
    }

    /**
     * Realiza el seteo de un metodo por reflexion.
     *
     * @param instancia    donde se va a realizar el setting.
     * @param parametro    a setear.
     * @param nombreMetodo nombre del metodo setter.
     *
     * @throws NoSuchMethodException     en caso de no encontrarse el metodo.
     * @throws IllegalAccessException    en caso de no ser un metodo publico.
     * @throws InvocationTargetException en caso de que el meotod ejecutado suelte una excepcion.
     * @noinspection ConstantConditions
     */
    public static void ejecutaMetodoSetter(Object instancia, Object parametro, String nombreMetodo) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
    {
        if (null == nombreMetodo)
            throw new NullArgumentException("<error>El argumento nombreMetodo no puede ser NULL.</error>");
        if (null == instancia)
            throw new NullArgumentException("<error>El argumento instancia no puede ser NULL. M&eacute;todo:" + nombreMetodo + "</error>");
        //Existir&aacute; la operaci&oacute;n setter?
        String metodo = buscaNombreMetodoSetter(instancia.getClass(), nombreMetodo);
        if (null == metodo)
            throw new NullPostConditionException("<error>El 'metodo' setter no se encontr&oacute;, buscado:" + nombreMetodo + ".</error>");

        //Lo buscamos en la clase.
        Method metodoValor = null;

        //Buscamos la clase &oacute;ptima para invocarlo
        final List<Class> clases = buscaClasesMetodoSetter(instancia.getClass(), metodo);

        if (1 == clases.size())
        {
            metodoValor = instancia.getClass().getMethod(metodo, clases.get(0));
        }
        else if (null == parametro)
        {
            throw new NullArgumentException("<error>El argumento 'parametro' no puede ser NULL cuando se tiene m&aacute;s de una operaci&oacute;n setter.</error>");
        }
        else
        {
            for (final Class claseParametro : clases)
            {
                try
                {
                    if (claseParametro.isInstance(parametro))
                    {
                        metodoValor = instancia.getClass().getMethod(metodo, claseParametro);
                        break;
                    }
                }
                catch (Exception e)
                {
                    s_log.error(e);
                }
            }
        }

        if (null == metodoValor)
            throw new NullPostConditionException("<error>A fallado una postcondici&oacute;n. El argumento de salida 'metodoValor' no puede ser NULL.</error>");

        //Lo invocamos
        metodoValor.invoke(instancia, parametro);
    }

    /**
     * Devuelve una objeto apartir de llamar un metodo de un objeto que obtenido de una operaci&oacute;n por notaci&oacute;n de punto.
     *
     * @param instancia    objeto en el que se va a buscar.
     * @param nombreMetodo Operaci&oacute;n a invocar para obtener el valor.
     *
     * @return Objeto valor.
     *
     * @throws NoSuchMethodException     en caso de no encontrarse el metodo.
     * @throws IllegalAccessException    en caso de no ser un metodo publico.
     * @throws InvocationTargetException en caso de que el meotod ejecutado suelte una excepcion.
     * @noinspection UseOfStringTokenizer
     */
    public static Object ejecutaFuncionGetter(final Object instancia, final String nombreMetodo) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
    {
        if (null == instancia)
            throw new NullArgumentException("<error>El argumento instancia no puede ser NULL.</error>");
        if (null == nombreMetodo)
            throw new NullArgumentException("<error>El argumento nombreMetodo no puede ser NULL.</error>");

        StringTokenizer st = new StringTokenizer(nombreMetodo, ".", false);
        Object valor = instancia;
        while (st.hasMoreTokens() && null != valor)
        {
            String metodoBuscado = st.nextToken();
            String metodo = buscaNombreMetodoGetter(valor.getClass(), metodoBuscado);
            if (null == metodo)
            {
                throw new NullPostConditionException("<error>El argumento 'metodo' no puede ser NULL. Obtenido desde la clase:" + instancia.getClass().getName() + ", metodo:" + nombreMetodo + ", buscando:" + metodoBuscado + "</error>");
            }
            valor = invocaMetodoEnMi(valor, metodo);
        }
        return valor;
    }

    /**
     * Devuelve una objeto apartir de llamar un metodo de un objeto que obtenido de una operaci&oacute;n por notaci&oacute;n de punto.
     * Esta operaci&oacute;n permite el recuperar un valor que se puede volver a ingresar en el setter mediante la 
     * operaci&oacute;n {@link RefleccionMetodos#ejecutaMetodoSetterBasicoCasting(Object, String, String)}.
     *
     * @param instancia    objeto en el que se va a buscar.
     * @param nombreMetodo operaci&oacute;n a invocar para obtener el valor.
     *
     * @return representaci&oacute;n textual del valor.  Vac&iacute;a si el resultado del getter es <code>null</code>.
     *
     * @noinspection UseOfStringTokenizer, OverlyStrongTypeCast
     */
    public static String ejecutaFuncionGetterBasicoCasting(final Object instancia, final String nombreMetodo)
    {
        try
        {
            final Object valor = ejecutaFuncionGetter(instancia, nombreMetodo);
            final String strValor;
            if (null == valor)
            {
                strValor = "";
            }
            else
            {
                final Class clazz = valor.getClass();
                if (clazz.isAssignableFrom(Date.class))
                {
                    strValor = S_SDF_FECHA_HORA.format((Date) valor);
                }
                else if (clazz.isAssignableFrom(java.sql.Date.class))
                {
                    strValor = S_SDF_FECHA_HORA.format((java.sql.Date) valor);
                }
                else if (clazz.isAssignableFrom(Timestamp.class))
                {
                    strValor = S_SDF_FECHA_HORA.format((Timestamp) valor);
                }
                else if (clazz.isAssignableFrom(BigDecimal.class))
                {
                    strValor = ((BigDecimal) valor).toPlainString();
                }
                else
                {
                    strValor = String.valueOf(valor);
                }
            }

            return strValor;
        }
        catch (final Exception e)
        {
            throw new RuntimeException(e);
        }
    }

    /**
     * Busca un metodo apartir del nombre de este y devuelve el valor de su returns.
     *
     * @param invocaEnMi    objeto donde se busca el metodo.
     * @param metodoBuscado nombre del metodo a buscar.
     *
     * @return valor devuelto por el metodo.
     *
     * @throws IllegalAccessException                      si no se puede invocar la operaci&oacute;n.
     * @throws NoSuchMethodException                       en caso de que la operaci&oacute;n buscado no exista.
     * @throws java.lang.reflect.InvocationTargetException la operaci&oacute;n solt&oacute; una excepci&oacute;n al invocarse.
     */
    private static Object invocaMetodoEnMi(Object invocaEnMi, String metodoBuscado) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException
    {
        if (null == invocaEnMi)
            throw new NullArgumentException("<error>El argumento 'invocaEnMi' no puede ser NULL.</error>");
        if (null == metodoBuscado)
            throw new NullArgumentException("<error>El argumento 'metodoBuscado' no puede ser NULL.</error>");
        if (0 == metodoBuscado.trim().length())
            throw new IllegalArgumentException("<error>El argumento 'metodoBuscado' no puede encontrarse vacio.</error>");

        final Method metodoValor = invocaEnMi.getClass().getMethod(metodoBuscado);
        return metodoValor.invoke(invocaEnMi);
    }

    /**
     * Busca el nombre de una operaci&oacute;n anteponiendole los valores is y get.
     *
     * @param buscaEnMi objeto donde se busca la operaci&oacute;n.
     * @param atributo  nombre base dla operaci&oacute;n.
     *
     * @return cadena con el nombre del metodo; <code>null</code> en caso de no encontrarlo.
     *
     * @noinspection StringToUpperCaseOrToLowerCaseWithoutLocale, ReturnOfNull
     */
    public static String buscaNombreMetodoGetter(Class buscaEnMi, String atributo)
    {
        if (null == atributo)
        {
            throw new NullArgumentException("<error>El argumento 'atributo' no puede ser NULL.</error>");
        }
        if (0 == atributo.trim().length())
        {
            throw new IllegalArgumentException("<error>El argumento 'atributo' es una cadena vacia.</error>");
        }
        String metodo = atributo;
        boolean encontrado = isMetodoExistente(buscaEnMi, atributo);
        if (!encontrado)
        {
            metodo = "get" + cambiaMayusculaPrimerLetraAtributo(atributo);
            encontrado = isMetodoExistente(buscaEnMi, metodo);
        }
        if (!encontrado)
        {
            metodo = "is" + cambiaMayusculaPrimerLetraAtributo(atributo);
            encontrado = isMetodoExistente(buscaEnMi, metodo);
        }

        return encontrado ? metodo : null;
    }

    /**
     * Devuelve un atributo con su primera letra en may&uacute;scula.
     *
     * @param atributo a alterar.
     *
     * @return String con el atributo.
     *
     * @noinspection StaticMethodNamingConvention, StringToUpperCaseOrToLowerCaseWithoutLocale
     */
    private static String cambiaMayusculaPrimerLetraAtributo(String atributo)
    {
        if (0 == atributo.trim().length())
            throw new IllegalArgumentException("<error>El argumento 'atributo' es una cadena vacia.</error>");
        return atributo.substring(0, 1).toUpperCase() + atributo.substring(1, atributo.length());
    }

    /**
     * Devuelve un atributo con su primera letra en minuscula.
     *
     * @param atributo a alterar.
     *
     * @return String con el atributo.
     *
     * @noinspection StaticMethodNamingConvention, StringToUpperCaseOrToLowerCaseWithoutLocale
     */
    private static String cambiaMinusculaPrimerLetraAtributo(String atributo)
    {
        if (0 == atributo.trim().length())
            throw new IllegalArgumentException("<error>El argumento 'atributo' es una cadena vacia.</error>");

        return atributo.substring(0, 1).toLowerCase() + atributo.substring(1, atributo.length());
    }

    /**
     * Busca el nombre de una operaci&oacute;n anteponiendole el valor set o solito.
     *
     * @param buscaEnMi       objeto donde se busca la operaci&oacute;n.
     * @param atributoMutable nombre del atributo a mutar.
     *
     * @return cadena con el nombre del metodo; <code>null</code> en caso de no encontrarlo.
     *
     * @noinspection StringToUpperCaseOrToLowerCaseWithoutLocale, ReturnOfNull
     */
    public static String buscaNombreMetodoSetter(Class buscaEnMi, String atributoMutable)
    {
        String metodo = atributoMutable;
        boolean encontrado = isMetodoExistente(buscaEnMi, metodo);
        if (!encontrado)
        {
            metodo = "set" + cambiaMayusculaPrimerLetraAtributo(atributoMutable);
            encontrado = isMetodoExistente(buscaEnMi, metodo);
        }

        return encontrado ? metodo : null;
    }

    /**
     * Nos dice si se encuentra una operaci&oacute;n dentro de la clase objeto.
     *
     * @param buscaEnMi     objeto donde se busca el metodo.
     * @param metodoBuscado nombre del metodo buscado.
     *
     * @return <code>true</code> si se encuentra el metodo; <code>false</code> en caso contrario.
     *
     * @noinspection CallToStringEquals
     */
    public static boolean isMetodoExistente(Class buscaEnMi, String metodoBuscado)
    {
        if (null == buscaEnMi)
            throw new NullArgumentException("<error>El argumento buscaEnMi no puede ser NULL.</error>");
        if (null == metodoBuscado)
            throw new NullArgumentException("<error>El argumento metodoBuscado no puede ser NULL.</error>");

        boolean salida = false;
        Method[] methods = buscaEnMi.getMethods();
        for (final Method method : methods)
        {
            if (method.getName().equals(metodoBuscado))
            {
                salida = true;
                break;
            }
        }

        return salida;
    }

    /**
     * Busca una operaci&oacute;n por su nombre dentro de la clase objeto.
     *
     * @param buscaEnMi     objeto donde se busca el metodo.
     * @param metodoBuscado nombre del metodo buscado.
     *
     * @return {@link java.lang.reflect.Method} en caso de encontrarlo; <code>null</code> en caso contrario.
     *
     * @noinspection CallToStringEquals
     */
    public static Method buscaMetodoPorNombre(Class buscaEnMi, String metodoBuscado)
    {
        if (null == buscaEnMi)
            throw new NullArgumentException("<error>El argumento buscaEnMi no puede ser NULL.</error>");
        if (null == metodoBuscado)
            throw new NullArgumentException("<error>El argumento metodoBuscado no puede ser NULL.</error>");

        Method salida = null;
        Method[] methods = buscaEnMi.getMethods();
        for (final Method method : methods)
        {
            if (method.getName().equals(metodoBuscado))
            {
                salida = method;
                break;
            }
        }

        return salida;
    }

    /**
     * Devuelve verdadero si la operaci&oacute;n es:
     * <ul>
     * <li>Un setter.</li>
     * <li>P&uacute;blico.</li>
     * <li>Establezca un primitivo, un date o un string.</li>
     * <li>Solo acepte un par&aacute;metro.</li>
     * </ul>
     * Verifica que una operaci&oacute;n sea un setter de un primitivo, String o Date.
     *
     * @param method a examinar.
     *
     * @return <code>true</code> si se cumple con las caracter&iacute;sticas; <code>false</code> en caso contrario.
     *
     * @noinspection StringToUpperCaseOrToLowerCaseWithoutLocale
     */
    public static boolean isSetterBasico(final Method method)
    {
        String nombreMetodo = method.getName();
        boolean salida = false;
        if (nombreMetodo.startsWith("set") && Modifier.isPublic(method.getModifiers()))
        {//Encontramos un Setter
            //Verificamos que tenga solo un par&aacute;metro y sea un primitivo o string o date.
            final Class[] parameterClasses = method.getParameterTypes();
            if (1 == parameterClasses.length && RefleccionClases.isClaseBasica(parameterClasses[0]))
            {
                salida = true;
            }
        }
        return salida;
    }

    /**
     * Devuelve verdadero si la operaci&oacute;n es:
     * <ul>
     * <li>Un getter.</li>
     * <li>P&uacute;blico.</li>
     * <li>Establezca un primitivo, un date o un string.</li>
     * <li>Solo acepte un par&aacute;metro.</li>
     * </ul>
     * Verifica que una operaci&oacute;n sea un setter de un primitivo, String o Date.
     *
     * @param method a examinar.
     *
     * @return <code>true</code> si se cumple con las caracter&iacute;sticas; <code>false</code> en caso contrario.
     *
     * @noinspection StringToUpperCaseOrToLowerCaseWithoutLocale
     */
    public static boolean isGetterBasico(final Method method)
    {
        String nombreMetodo = method.getName();
        boolean salida = false;
        if ((nombreMetodo.startsWith("get") || nombreMetodo.startsWith("is")) && Modifier.isPublic(method.getModifiers()))
        {//Encontramos un Setter
            //Verificamos que tenga solo un par&aacute;metro y sea un primitivo o string o date.
            final Class returnType = method.getReturnType();
            if (RefleccionClases.isClaseBasica(returnType) && 0 == method.getParameterTypes().length)
            {
                salida = true;
            }
        }
        return salida;
    }

    /**
     * Busca el nombre de un atributo establecido en un setter o getter.
     *
     * @param method del cual se busca el nombre del atributo a mutar o leer.
     *
     * @return String con el nombre; <code>null</code> en caso de no ser un getter ni un setter.
     *
     * @noinspection TypeMayBeWeakened
     */
    public static String nombreAtributoSetterOGetter(final Method method)
    {
        final String name = method.getName();
        String salida = null;

        if (name.startsWith("is"))
        {
            salida = cambiaMinusculaPrimerLetraAtributo(name.substring(2, name.length()));
        }
        else if (name.startsWith("set") || name.startsWith("get"))
        {
            salida = cambiaMinusculaPrimerLetraAtributo(name.substring(3, name.length()));
        }
        return salida;
    }

    /**
     * Busca la clase que ser&aacute; dada por par&aacute;metro para la invocaci&oacute;n dla operaci&oacute;n setter. Es necesario hacer
     * esto por que cuando una clase extiende es posible realizar el seteo pero reflection exije que
     * la clase sea la misma.
     *
     * @param claseInvocaEnMi clase donde se buscar&aacute; la operaci&oacute;n.
     * @param nombreMetodo    buscado.
     *
     * @return Class a establecer en la invocaci&oacute;n del setter.
     */
    public static List<Class> buscaClasesMetodoSetter(Class claseInvocaEnMi, String nombreMetodo)
    {
        if (null == claseInvocaEnMi)
            throw new NullArgumentException("<error>El argumento 'claseInvocaEnMi' no puede ser NULL.</error>");
        if (null == nombreMetodo)
            throw new NullArgumentException("<error>El argumento 'nombreMetodo' no puede ser NULL.</error>");
        if (0 == nombreMetodo.trim().length())
            throw new IllegalArgumentException("<error>El argumento 'nombreMetodo' es una cadena vacia.</error>");


        final List<Class> tipos = new ArrayList<Class>();
        Method[] methods = claseInvocaEnMi.getMethods();
        for (final Method method : methods)
        {
            if (method.getName().equals(nombreMetodo))
            {
                final Class[] methodTypes = method.getParameterTypes();
                if (1 == methodTypes.length) tipos.add(methodTypes[0]);
            }
        }

        if (0 == tipos.size())
            throw new NullPostConditionException("<error><msg>A fallado una postcondici&oacute;n. No se encontr&oacute; una operaci&oacute;n de un solo argumento.</msg><param>" + nombreMetodo + "</param></error>", claseInvocaEnMi);
        return tipos;
    }

    /**
     * Devuelve un listado de todos los nombres de los atributos que tienen una operaci&oacute;n getter y una operaci&oacute;n setter.
     *
     * @param examiname pojo donde se buscar&aacute;n los atributos.
     *
     * @return <code>List</code> con los nombres de los atributos; vac&iacute;a s&iacute; ning&uacute;n atributo <code>this#isSetterBasico</code> y <code>this#isGetterBasico</code>.
     */
    public static List<String> listaNombresAtributosBasicos(final Class examiname)
    {
        if (null == examiname)
            throw new NullArgumentException("<error>El argumento 'examiname' no puede ser NULL.</error>");

        final List<String> atributos = new ArrayList<String>();
        final Method[] methods = examiname.getMethods();
        for (final Method method : methods)
        {
            if (isGetterBasico(method))
            {
                final String nombreAtributo = nombreAtributoSetterOGetter(method);
                final String nombreMetodoSetter = "set" + cambiaMayusculaPrimerLetraAtributo(nombreAtributo);
                final Method setter = buscaMetodoPorNombre(examiname, nombreMetodoSetter);
                if (null != setter && isSetterBasico(setter) && !atributos.contains(nombreAtributo))
                {
                    atributos.add(nombreAtributo);
                }
            }
        }
        return atributos;
    }


    /**
     * Devuelve un listado de todos los nombres de los atributos que se encuentran anotados por la anotaci&oacute;n par&oacute;metro..
     *
     * @param examiname       pojo donde se buscar&aacute;n los atributos.
     * @param annotationClass clase de la anotaci&oacute;n buscada.
     *
     * @return <code>List</code> con los nombres de los atributos; vac&iacute;a s&iacute; ning&uacute;n atributo anotado.
     */
    public static List<String> listaNombresAtributosAnotados(final Class examiname, final Class<? extends Annotation> annotationClass)
    {
        if (null == examiname)
            throw new NullArgumentException("<error>El argumento 'examiname' no puede ser NULL.</error>");
        if (null == annotationClass)
            throw new NullArgumentException("<error>El argumento 'annotationClass' no puede ser NULL.</error>");

        final List<String> atributos = new ArrayList<String>();
        final Field[] declaredFields = examiname.getDeclaredFields();
        for (final Field declaredField : declaredFields)
        {
            if (declaredField.isAnnotationPresent(annotationClass))
            {
                final String name = declaredField.getName();
                final String nombreAtributo = name.startsWith("m_") && name.length() > 2 ? name.substring(2, name.length()) : name;
                final String nombreMetodoGetter = buscaNombreMetodoGetter(examiname, nombreAtributo);
                final String nombreMetodoSetter = buscaNombreMetodoSetter(examiname, nombreAtributo);
                if (null != nombreMetodoGetter && null != nombreMetodoSetter)
                {
                    atributos.add(nombreAtributo);
                }
            }
        }
        return atributos;
    }


}




